package com.appspot.theonionorthenews;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Base servlet class that ensures a {@link EntityManager} is created for
 * each web request, thus ensuring isolation.  The @{code EntityManager}
 * will be stored in a thread local variable.
 */
@SuppressWarnings("serial")
public abstract class BaseServlet extends HttpServlet {

  /** Location header name, e.g. for redirection. */
  protected static final String LOCATION = "Location";
  /** Mime type for HTML text pages. */
  protected static final String TEXT_HTML = "text/html";
  /** Mime type for plain text pages, used for some "unlikely" errors. */
  protected static final String TEXT_PLAIN = "text/plain";

  /** URL parameter for an object's draw id. */
  protected static final String DRAW_PARAM = "d";
  /** Form replacement token for page's URL. */
  private static final String PAGE_URL_TOKEN = "%URL%";
  protected static final int ID_RADIX = 16;
  /** URL parameter for the id of the item being answered. */
  protected static final String ID_PARAM = "id";

  /** Thread-specific entity manager. */
  private ThreadLocal<EntityManager> local = new ThreadLocal<EntityManager>();

  /** Escapes HTML-special characters into their ampersand form. */
  public static String escapeAsHtml(String raw) {
    if (raw == null) {
      return null;
    }
 
    StringBuilder answer = new StringBuilder(raw.length());

    for (int i = 0; i < raw.length(); i++) {
      char ch = raw.charAt(i);
      switch (ch) {
      case '&':
        answer.append("&amp;");
        break;
      case '<':
        answer.append("&lt;");
        break;
      case '>':
        answer.append("&gt;");
        break;
      case '"':
        answer.append("&quot;");
        break;
      default:
        answer.append(ch);
      }
    }
    return null;
  }

  /** Escapes quote characters with a backslash. */
  public static String escapeQuotes(String raw) {
    return raw.replaceAll("\"", "\\\"").replaceAll("'", "\\'");
  }

  /**
   * Renders a page from a template, substituting parameters for %...% tokens.
   * 
   * @param out the output PrintWriter.
   * @param template the template of the page
   * @param pageUri the URL to substitute for {@code PAGE_URL_TOKEN}.
   * @param params a map of token to substitution; for every key <i>key</i> in
   *    {@code params}, "%<i>key</i>%" will be replaced by its value.
   * @throws IOException
   */
  protected static void renderForm(PrintWriter out, String template, String pageUri,
      Map<String, String> params) throws IOException {
    if (params != null) {
      for (Map.Entry<String, String> entry : params.entrySet()) {
        // Note that "replace" is a replace-all (but replaceAll() does regexps)
        template = template.replace("%" + entry.getKey() + "%", entry.getValue());
      }
      template = template.replace(PAGE_URL_TOKEN, pageUri);
    }
    out.print(template);
  }

  public BaseServlet() {
    super();
  }

  /**
   * Handles a request, ensuring that if an {@link EntityManager} is created
   * during the request, it is closed and discarded afterwards.
   */
  @Override
  public final void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    assert local.get() == null;
    try {
      super.service(req, resp);
    } finally {
      EntityManager em = local.get();
      if (em != null) {
        em.close();
      }
      local.set(null);
    }
  }

  /** Creates an entity manager, overridable if the {@code Map}-using form is needed. */
  protected EntityManager createEntityManager(EntityManagerFactory emf) {
    return emf.createEntityManager();
  }

  /** Gets the entity manager, creating it if necessary. */
  protected EntityManager getEntityManager() {
    if (local.get() == null) {
      EntityManagerFactory emf = EmfSingleton.getInstance();
      local.set(createEntityManager(emf));
    }
    return local.get();  // Thread safety isn't a concern; it's thread-local!
  }

  /** Loads a resource stream into a String. */
  protected String readStringFromResource(String name) throws IOException {
    StringBuilder builder = new StringBuilder();
    byte buffer[] = new byte[2048];

    InputStream in = getClass().getResourceAsStream(name);
    if (in == null) {
      return null;
    }
    int num = in.read(buffer);
    while (num >= 0) {
      builder.append(new String(buffer, "UTF-8"));
      num = in.read(buffer);
    }
    return builder.toString();
  }

  /** Convenience for brief but ugly error pages. */
  protected void sendTextError(HttpServletResponse resp, int status, String errorText) {
    resp.setStatus(status);
    resp.setContentType("text/plain");
    try {
      resp.getWriter().print(errorText);
    } catch (IOException e) {
      // oh, well.  We tried.
    }
  }
}